package com.behelpful.demo.gateway.locator;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSONObject;
import com.behelpful.demo.admin.entity.GatewayRouteInDb;
import com.behelpful.demo.admin.service.GatewayRouteService;
import com.behelpful.demo.gateway.entity.RouteDefinitionVo;
import com.behelpful.common.integration.iredis.constants.CommonRedisCacheEnum;
import com.behelpful.common.integration.iredis.support.IRedisService;
import com.behelpful.common.integration.iredis.support.IRedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import redis.clients.jedis.Jedis;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 路由加载器
 * 1、启动时加载路由写入 Redis
 * 2、访问是从缓存中获取
 *
 * @author Alay
 * @date 2021-09-05 09:00
 * @since
 */
@Slf4j
@Component(value = "routeWriterAndReader")
public class RouteDefinitionWriterAndReader implements RouteDefinitionRepository, InitializingBean {

    /**
     * 可以使用 RedisTemplate ,根据个人使用习惯定就行
     */
    @Autowired
    private IRedisUtil iRedisUtil;
    @Autowired
    private IRedisService iRedisService;
    /**
     * Gateway 并不需要数据库读写相关的配置,通过远程服务调用查询数据库中 Gateway的配置数据即可
     */
    @DubboReference
    private GatewayRouteService gatewayRouteService;

    /**
     * 初始化路由到 Redis
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() {
        // 清除Redis中缓存的路由
        iRedisService.delAsKey(CommonRedisCacheEnum.ROUTE_HASH_DATA.getKey());
        // 加载数据库中所有路由信息载入到 Redis
        List<GatewayRouteInDb> routes = gatewayRouteService.list();
        if (routes.isEmpty()) {
            return;
        }
        Jedis jedis = null;
        try {
            jedis = iRedisUtil.getJedis();
            for (GatewayRouteInDb routeInDb : routes) {
                // 数据库中的路由信息转换为 Gateway 中的路由对象信息
                RouteDefinitionVo routeVo = new RouteDefinitionVo();
                routeVo.setRouteName(routeInDb.getRouteName());
                routeVo.setId(routeInDb.getRouteId());

                // 断言解析
                List<PredicateDefinition> predicates = JSONObject.parseArray(routeInDb.getPredicates(), PredicateDefinition.class);
                routeVo.setPredicates(predicates);

                // 过滤器解析
                List<FilterDefinition> filters = JSONObject.parseArray(routeInDb.getFilters(), FilterDefinition.class);
                routeVo.setFilters(filters);

                // URI 解析
                routeVo.setUri(URI.create(routeInDb.getUri()));
                routeVo.setOrder(routeInDb.getOrder());

                // 元数据解析
                String metadata = routeInDb.getMetadata();
                JSONObject metadataJson = JSONObject.parseObject(metadata);
                routeVo.setMetadata(metadataJson);

                // 路由数据存入缓存
                String routeStr = JSONObject.toJSONString(routeVo);
                jedis.hset(CommonRedisCacheEnum.ROUTE_HASH_DATA.getKey(), routeVo.getId(), routeStr);
                log.info("加载路由信息 : {}", routeStr);
            }
        } finally {
            iRedisUtil.close(jedis);
        }
    }

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        Jedis jedis = null;
        List<RouteDefinitionVo> routes = new ArrayList<>();
        try {
            jedis = iRedisUtil.getJedis();
            Map<String, String> routeMap = jedis.hgetAll(CommonRedisCacheEnum.ROUTE_HASH_DATA.getKey());
            if (CollUtil.isNotEmpty(routeMap)) {
                // 将路由信息提去转成 List
                routes = routeMap.entrySet().stream()
                        .map(entity -> JSONObject.parseObject(entity.getValue(), RouteDefinitionVo.class))
                        .collect(Collectors.toList());
            }
        } finally {
            iRedisUtil.close(jedis);
        }
        if (routes.isEmpty()) {
            return Flux.empty();
        }
        return Flux.fromIterable(routes);
    }


    /**
     * 新增路由时,保存路由信息
     *
     * @param route
     * @return
     */
    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        Jedis jedis = null;
        try {
            jedis = iRedisUtil.getJedis();
            RouteDefinitionVo routeInDb = BeanUtil.copyProperties(route, RouteDefinitionVo.class);
            String routeJsonStr = JSONObject.toJSONString(routeInDb);
            jedis.hset(CommonRedisCacheEnum.ROUTE_HASH_DATA.getKey(), routeInDb.getId(), routeJsonStr);
            log.info("保存路由信息： {}", routeJsonStr);
            return Mono.empty();
        } finally {
            iRedisUtil.close(jedis);
        }
    }

    /**
     * 删除路由
     *
     * @param routeId
     * @return
     */
    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        Jedis jedis = null;
        try {
            jedis = iRedisUtil.getJedis();
            Jedis finalJedis = jedis;
            routeId.flatMap(rId -> {
                Long del = finalJedis.del(CommonRedisCacheEnum.ROUTE_HASH_DATA.getKey(), rId);
                if (1 == del) {
                    log.info("成功删除路由：routeId={}", rId);
                    return Mono.empty();
                } else {
                    // 删除的路由信息不存在
                    return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition not found: " + routeId)));
                }
            });
        } finally {
            iRedisUtil.close(jedis);
        }
        return Mono.empty();
    }

}
